using System;
using System.Collections;
using System.IO;

class SODToRaw
{
    static string s_strFileBase = "";
    static bool _flip = false;

    static Matrix4X4 Read3X4Matrix(BinaryReader br)
    {
        Matrix4X4 mat = new Matrix4X4();

        mat.m11 = br.ReadSingle();
        mat.m12 = br.ReadSingle();
        mat.m13 = br.ReadSingle();
        mat.m14 = 0.0f;

        mat.m21 = br.ReadSingle();
        mat.m22 = br.ReadSingle();
        mat.m23 = br.ReadSingle();
        mat.m24 = 0.0f;

        mat.m31 = br.ReadSingle();
        mat.m32 = br.ReadSingle();
        mat.m33 = br.ReadSingle();
        mat.m34 = 0.0f;

        mat.m41 = br.ReadSingle();
        mat.m42 = br.ReadSingle();
        mat.m43 = br.ReadSingle();
        mat.m44 = 1.0f;

        return mat;
    }

    static string ReadIdentifier(BinaryReader br)
    {
        int cch = br.ReadInt16();

        if(cch == 0)
        {
            return "";
        }

        return new String(br.ReadChars(cch)).ToLower();
    }

    static float GetMatrix(ArrayList matrix, int col, int row)
    {
        ArrayList rowList = (ArrayList) (matrix[row]);

        return (float) rowList[col];
    }

    static void Transform(ref float x, ref float y, ref float z, ArrayList matrix)
    {
        float newX = x * GetMatrix(matrix, 0, 0) + y * GetMatrix(matrix, 0, 1) + z * GetMatrix(matrix, 0, 2) + GetMatrix(matrix, 0, 3);
        float newY = x * GetMatrix(matrix, 1, 0) + y * GetMatrix(matrix, 1, 1) + z * GetMatrix(matrix, 1, 2) + GetMatrix(matrix, 1, 3);
        float newZ = x * GetMatrix(matrix, 2, 0) + y * GetMatrix(matrix, 2, 1) + z * GetMatrix(matrix, 2, 2) + GetMatrix(matrix, 2, 3);

        x = newX;
        y = newY;
        z = newZ;
    }

    struct Vector3
    {
        public float x;
        public float y;
        public float z;

        public void Normalize()
        {
            float flLength = Length();

            if(flLength > 0.001)
            {
                x /= flLength;
                y /= flLength;
                z /= flLength;
            }
            else
            {
                x = y = z = 0;
            }
        }

        public float Length()
        {
            return (float)Math.Sqrt(x * x + y * y + z * z);
        }
    };

    struct Texture2
    {
        public float u;
        public float v;
    }

    struct Face
    {
        public int one;
        public int two;
        public int three;
    }

    struct FixupInfo
    {
        public int faceIndex;
        public int vertexIndex;
    }

    class Matrix4X4
    {
        public float m11, m12, m13, m14;
        public float m21, m22, m23, m24;
        public float m31, m32, m33, m34;
        public float m41, m42, m43, m44;

        public Matrix4X4()
        {
            m11 = m12 = m13 = m14 = 0.0f;
            m21 = m22 = m23 = m24 = 0.0f;
            m31 = m32 = m33 = m34 = 0.0f;
            m41 = m42 = m43 = m44 = 0.0f;
            m11 = m22 = m33 = m44 = 1.0f;
        }

        static public Matrix4X4 RotateZ(float flRadian)
        {
            Matrix4X4 mat = new Matrix4X4();

            mat.m11 = (float)Math.Cos(flRadian);
            mat.m12 = (float)Math.Sin(flRadian);
            mat.m21 = -1.0f * (float)Math.Sin(flRadian);
            mat.m22 = (float)Math.Cos(flRadian);

            return mat;
        }

        static public Matrix4X4 RotateX(float flRadian)
        {
            Matrix4X4 mat = new Matrix4X4();

            mat.m22 = (float)Math.Cos(flRadian);
            mat.m23 = (float)Math.Sin(flRadian);
            mat.m32 = -1.0f * (float)Math.Sin(flRadian);
            mat.m33 = (float)Math.Cos(flRadian);

            return mat;
        }


        public Vector3 Transform(Vector3 vec)
        {
            Vector3 vecReturn;

            vecReturn.x = vec.x * m11 + vec.y * m21 + vec.z * m31 + 1.0f * m41;
            vecReturn.y = vec.x * m12 + vec.y * m22 + vec.z * m32 + 1.0f * m42;
            vecReturn.z = vec.x * m13 + vec.y * m23 + vec.z * m33 + 1.0f * m43;

            return vecReturn;
        }

        static public Matrix4X4 Mult(Matrix4X4 one, Matrix4X4 two)
        {
            Matrix4X4 result = new Matrix4X4();
            for(int row = 0; row < 4; row++)
            {
                for(int col = 0; col < 4; col++)
                {
                    float flSum = 0.0f;

                    for(int i = 0; i < 4; i++)
                    {
                        flSum += one[row, i] * two[i, col];
                    }

                    result[row, col] = flSum;
                }
            }

            return result;
        }

        float this[int row, int col]
        {
            get
            {
                if(row == 0)
                {
                    if(col == 0) return m11;
                    if(col == 1) return m12;
                    if(col == 2) return m13;
                    if(col == 3) return m14;
                }
                else if(row == 1)
                {
                    if(col == 0) return m21;
                    if(col == 1) return m22;
                    if(col == 2) return m23;
                    if(col == 3) return m24;
                }
                else if(row == 2)
                {
                    if(col == 0) return m31;
                    if(col == 1) return m32;
                    if(col == 2) return m33;
                    if(col == 3) return m34;
                }
                else
                {
                    if(col == 0) return m41;
                    if(col == 1) return m42;
                    if(col == 2) return m43;
                    if(col == 3) return m44;
                }
                return 0.0f;

            }
            set
            {
                if(row == 0)
                {
                    if(col == 0) m11 = value;
                    if(col == 1) m12 = value;
                    if(col == 2) m13 = value;
                    if(col == 3) m14 = value;
                }
                else if(row == 1)
                {
                    if(col == 0) m21 = value;
                    if(col == 1) m22 = value;
                    if(col == 2) m23 = value;
                    if(col == 3) m24 = value;
                }
                else if(row == 2)
                {
                    if(col == 0) m31 = value;
                    if(col == 1) m32 = value;
                    if(col == 2) m33 = value;
                    if(col == 3) m34 = value;
                }
                else
                {
                    if(col == 0) m41 = value;
                    if(col == 1) m42 = value;
                    if(col == 2) m43 = value;
                    if(col == 3) m44 = value;
                }

            }

        }
    }

    class SODMaterial
    {
        public string Name;
        public float flAR, flAG, flAB;
        public float flDR, flDG, flDB;
        public float flSR, flSG, flSB;
        public float flShininess;
        public byte bModel;
    }

    class BaseNode
    {
        public ArrayList Children = new ArrayList();
        public string Name;
        public string ParentName;
        public Matrix4X4 Matrix;
    }

    class MeshNode : BaseNode
    {
        public string strMaterial;
        public string strTexture;
        public Face[] faceArray;
        public Vector3[] vectorArray;
        public Vector3[] normalArray;
        public Texture2[] textureArray;

        public void CalculateNormals()
        {
            if(vectorArray.Length == 0)
            {
                return;
            }

            normalArray = new Vector3[vectorArray.Length];

            for(int normalIndex = 0; normalIndex < normalArray.Length; normalIndex++)
            {
                Vector3 normal = new Vector3();
                for(int faceIndex = 0; faceIndex < faceArray.Length; faceIndex++)
                {
                    Face face = faceArray[faceIndex];
                    if(face.one == normalIndex || face.two == normalIndex || face.three == normalIndex)
                    {
                        Vector3 faceNormal = CalculateFaceNormal(faceIndex);

                        normal.x += faceNormal.x;
                        normal.y += faceNormal.y;
                        normal.z += faceNormal.z;
                    }
                }
                normal.Normalize();
                normalArray[normalIndex] = normal;
            }
        }

        Vector3 CalculateFaceNormal(int faceIndex)
        {
            Face face = faceArray[faceIndex];
            Vector3 twoToOne;
            Vector3 threeToOne;

            twoToOne.x = vectorArray[face.two].x - vectorArray[face.one].x;
            twoToOne.y = vectorArray[face.two].y - vectorArray[face.one].y;
            twoToOne.z = vectorArray[face.two].z - vectorArray[face.one].z;

            threeToOne.x = vectorArray[face.three].x - vectorArray[face.one].x;
            threeToOne.y = vectorArray[face.three].y - vectorArray[face.one].y;
            threeToOne.z = vectorArray[face.three].z - vectorArray[face.one].z;

            Vector3 ret;

            ret.x = twoToOne.y * threeToOne.z - twoToOne.z * threeToOne.y;
            ret.y = twoToOne.z * threeToOne.x - twoToOne.x * threeToOne.z;
            ret.z = twoToOne.x * threeToOne.y - twoToOne.y * threeToOne.x;

            ret.Normalize();

            return ret;
        }
    }

    class NullNode : BaseNode
    {
    }

    class LodNode : BaseNode
    {
    }

    class SpriteNode : BaseNode
    {
    }

    class EmitterNode : BaseNode
    {
    }

    static Matrix4X4 GenerateMeshFixupTransform(Hashtable ht)
    {
        MeshNode meshNode = null;
        float maxX = 0.0f;
        float minX = 0.0f;
        float maxY = 0.0f;
        float minY = 0.0f;
        float maxZ = 0.0f;
        float minZ = 0.0f;
        bool isFirst = true;

        foreach(object key in ht.Keys)
        {
            meshNode = ht[key] as MeshNode;
            if(meshNode == null)
                continue;

            if(isFirst)
            {
                isFirst = false;
                maxX = meshNode.vectorArray[0].x;
                minX = meshNode.vectorArray[0].x;
                maxY = meshNode.vectorArray[0].y;
                minY = meshNode.vectorArray[0].y;
                maxZ = meshNode.vectorArray[0].z;
                minZ = meshNode.vectorArray[0].z;
            }

            for(int i = 0; i < meshNode.vectorArray.Length; i++)
            {
                maxX = Math.Max(maxX, meshNode.vectorArray[i].x);
                minX = Math.Min(minX, meshNode.vectorArray[i].x);

                maxY = Math.Max(maxY, meshNode.vectorArray[i].y);
                minY = Math.Min(minY, meshNode.vectorArray[i].y);

                maxZ = Math.Max(maxZ, meshNode.vectorArray[i].z);
                minZ = Math.Min(minZ, meshNode.vectorArray[i].z);
            }
        }
        // Center on all three axis.

        float xDiff = - ((maxX + minX) / 2.0f);
        float yDiff = - ((maxY + minY) / 2.0f);
        float zDiff = - ((maxZ + minZ) / 2.0f);

        // Unit sphere, so length should scale to 2.0
        float flScale = 2.0f / Math.Max(Math.Max( maxX - minX,  maxY - minY), maxZ - minZ);

        Matrix4X4 mat = new Matrix4X4();
        mat.m11 = mat.m22 = mat.m33 = flScale;
        mat.m41 = xDiff * flScale;
        mat.m42 = yDiff * flScale;
        mat.m43 = zDiff * flScale;

        return mat;
    }

    static void UpdateVectorsArrayToExportCoordinates(ref Vector3[] vectorsArray, float xDiff, float yDiff, float zDiff, float flScale)
    {
        for(int i = 0; i < vectorsArray.Length; i++)
        {
            vectorsArray[i].x = (vectorsArray[i].x + xDiff) * flScale;
            vectorsArray[i].y = (vectorsArray[i].y + yDiff) * flScale;
            vectorsArray[i].z = (vectorsArray[i].z + zDiff) * flScale;
        }

        for(int i = 0; i < vectorsArray.Length; i++)
        {
            float flRotZ = 3.14f;
            float x = vectorsArray[i].x;
            float y = vectorsArray[i].y;
            float z = vectorsArray[i].z;

            vectorsArray[i].x = (x * ((float)Math.Cos(flRotZ)) + (y * ((float)Math.Sin(flRotZ))));
            vectorsArray[i].y = (-x * ((float)Math.Sin(flRotZ)) + (y * ((float)Math.Cos(flRotZ))));
            vectorsArray[i].z = z;
        }

        for(int i = 0; i < vectorsArray.Length; i++)
        {
            float flRotX = -(3.14f / 2.0f);
            float x = vectorsArray[i].x;
            float y = vectorsArray[i].y;
            float z = vectorsArray[i].z;

            vectorsArray[i].x = x;
            vectorsArray[i].y = (y * ((float)Math.Cos(flRotX)) + (z * ((float)Math.Sin(flRotX))));;
            vectorsArray[i].z = (-y * ((float)Math.Sin(flRotX)) + (z * ((float)Math.Cos(flRotX))));
        }
        
    }

    static MeshNode ParseMeshNode(BinaryReader br, Matrix4X4 mat)
    {
        MeshNode meshNode = new MeshNode();

        meshNode.strMaterial = ReadIdentifier(br);
        if(s_flVersion > 1.8)
        {
            int unknown1 = br.ReadInt32();
            int unknown2 = br.ReadInt32();
        }
		meshNode.strTexture = ReadIdentifier(br);

        Console.WriteLine("Material: " + meshNode.strMaterial);
        Console.WriteLine("Texture: " + meshNode.strTexture);

        if(s_flVersion > 1.8)
        {
            int unknown3 = br.ReadInt32();
            int unknown4 = br.ReadInt32();
        }

        int vertexCount = br.ReadUInt16();
        int texCoordCount = br.ReadUInt16();
        int faceGroupCount = br.ReadUInt16();

        Vector3[] arrayVectors = new Vector3[vertexCount];
        Texture2[] arrayTexture = new Texture2[texCoordCount];

        for(int i = 0; i < vertexCount; i++)
        { 
            arrayVectors[i].x = br.ReadSingle();
            arrayVectors[i].y = br.ReadSingle();
            arrayVectors[i].z = br.ReadSingle();

            arrayVectors[i] = mat.Transform(arrayVectors[i]);
        }

        for(int i = 0; i < texCoordCount; i++)
        { 
            arrayTexture[i].u = br.ReadSingle();
            arrayTexture[i].v = br.ReadSingle();
        }

        Face[] arrayFaces = null;
        Face[] arrayFaceTextures = null;

        Console.WriteLine("Face group count: " + faceGroupCount);

        for(int i = 0; i < faceGroupCount; i++)
        {
            int numFaces = br.ReadUInt16();
            int startIndex = 0;
            string strGroupMaterial = ReadIdentifier(br);

            Console.WriteLine("Group Material: " + strGroupMaterial);

            if(arrayFaces == null)
            {
                arrayFaces = new Face[numFaces];
                arrayFaceTextures = new Face[numFaces];
            }
            else
            {
                Face[] newArrayFaces = new Face[arrayFaces.Length + numFaces];
                Face[] newArrayFaceTextures = new Face[arrayFaces.Length + numFaces];

                Array.Copy(arrayFaces, newArrayFaces, arrayFaces.Length);
                Array.Copy(arrayFaceTextures, newArrayFaceTextures, arrayFaceTextures.Length);
                startIndex = arrayFaces.Length;
                arrayFaces = newArrayFaces;
                arrayFaceTextures = newArrayFaceTextures;
            }

            for(int j = 0; j < numFaces; j++)
            {
                Face face;
                Face faceTexture;

                face.one = br.ReadUInt16();
                faceTexture.one = br.ReadUInt16();
                face.two = br.ReadUInt16();
                faceTexture.two = br.ReadUInt16();
                face.three = br.ReadUInt16();
                faceTexture.three = br.ReadUInt16();

                arrayFaces[startIndex + j] = face;
                arrayFaceTextures[startIndex + j] = faceTexture;
            }
        }

        ArrayList fixupList = new ArrayList();

        for(int vertexIndex = 0; vertexIndex < arrayVectors.Length; vertexIndex++)
        {
            int textureAssigned = 0;
            bool isFirst = true;
            for(int faceIndex = 0; faceIndex < arrayFaces.Length; faceIndex++)
            {
                Face face = arrayFaces[faceIndex];
                if(face.one == vertexIndex || face.two == vertexIndex || face.three == vertexIndex)
                {
                    int textureIndex = 0;

                    if(face.one == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].one;
                    }
                    if(face.two == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].two;
                    }
                    if(face.three == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].three;
                    }

                    if(isFirst)
                    {
                        textureAssigned = textureIndex;
                        isFirst = false;
                    }
                    else
                    {
                        if(textureIndex != textureAssigned)
                        {
                            FixupInfo fixupInfo;

                            fixupInfo.faceIndex = faceIndex;
                            fixupInfo.vertexIndex = vertexIndex;


                            fixupList.Add(fixupInfo);
                        }
                    }
                }
                
            }
        }

        // Because of fixup, we're going to be adding a number of new verts, split from the originals.
        Vector3[] newArrayVectors = new Vector3[arrayVectors.Length + fixupList.Count];

        Array.Copy(arrayVectors, newArrayVectors, arrayVectors.Length);

        int fixupStartIndex = arrayVectors.Length;
        arrayVectors = newArrayVectors;

        for(int fixupIndex = 0; fixupIndex < fixupList.Count; fixupIndex++)
        {
            FixupInfo fixupInfo = (FixupInfo) fixupList[fixupIndex];
            int faceIndex = fixupInfo.faceIndex;
            int vertexIndex = fixupInfo.vertexIndex;

            arrayVectors[fixupStartIndex + fixupIndex] = arrayVectors[vertexIndex]; // Duplicate this vert.

            if(arrayFaces[faceIndex].one == vertexIndex)
                arrayFaces[faceIndex].one = fixupStartIndex + fixupIndex;

            if(arrayFaces[faceIndex].two == vertexIndex)
                arrayFaces[faceIndex].two = fixupStartIndex + fixupIndex;

            if(arrayFaces[faceIndex].three == vertexIndex)
                arrayFaces[faceIndex].three = fixupStartIndex + fixupIndex;
        }

        Texture2[] finalArrayTexture = new Texture2[arrayVectors.Length];

        for(int vertexIndex = 0; vertexIndex < arrayVectors.Length; vertexIndex++)
        {
            bool isAssigned = false;

            for(int faceIndex = 0; faceIndex < arrayFaces.Length && !isAssigned; faceIndex++)
            {
                Face face = arrayFaces[faceIndex];
                if(face.one == vertexIndex || face.two == vertexIndex || face.three == vertexIndex)
                {
                    int textureIndex = 0;

                    if(face.one == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].one;
                    }
                    if(face.two == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].two;
                    }
                    if(face.three == vertexIndex)
                    {
                        textureIndex = arrayFaceTextures[faceIndex].three;
                    }
                    finalArrayTexture[vertexIndex] = arrayTexture[textureIndex];
                    isAssigned = true;
                }
            }
        }

        meshNode.faceArray = arrayFaces;
        meshNode.vectorArray = arrayVectors;
        meshNode.textureArray = finalArrayTexture;

        meshNode.CalculateNormals();

        byte bBackface = br.ReadByte();

        Console.WriteLine("Backface: " + bBackface);

        int shouldBeZero = br.ReadInt16();

        return meshNode;
    }

    static void ExportMeshNode(MeshNode meshNode, Matrix4X4 finalTransform, Matrix4X4 normalTransform, Hashtable htMaterials, TextWriter meshFile)
    {
        if(meshNode.strTexture == "")
        {
            throw new InvalidOperationException();
        }

        string strMaterialName = "Material_" + s_strFileBase + "_" + meshNode.Name;
        if(htMaterials[meshNode.strTexture] != null)
        {
            strMaterialName = (string)htMaterials[meshNode.strTexture];
        }
        else
        {
            htMaterials[meshNode.strTexture] = strMaterialName;
        }

        meshFile.WriteLine("\t\t<submesh material=\"" + strMaterialName +"\" usesharedvertices=\"false\" use32bitindexes=\"false\" operationtype=\"triangle_list\">");

        if(_flip)
        {
            Console.WriteLine("Flipping: " + meshNode.Name);
        }

        meshFile.WriteLine("\t\t\t<faces count=\""+meshNode.faceArray.Length+"\">");
        for(int i = 0; i < meshNode.faceArray.Length; i++)
        {
            Face face = meshNode.faceArray[i];
            if(_flip)
            {
                meshFile.WriteLine("\t\t\t\t<face v1=\"" + face.one + "\" v2=\"" + face.three + "\" v3=\"" + face.two + "\" />");
            }
            else
            {
                meshFile.WriteLine("\t\t\t\t<face v1=\"" + face.one + "\" v2=\"" + face.two + "\" v3=\"" + face.three + "\" />");
            }
        }
        meshFile.WriteLine("\t\t\t</faces>");

        meshFile.WriteLine("\t\t\t<geometry vertexcount=\"" + meshNode.vectorArray.Length +"\">");
        meshFile.WriteLine("\t\t\t\t<vertexbuffer positions=\"true\" normals=\"true\">");

        for(int i = 0; i < meshNode.vectorArray.Length; i++)
        {
            Vector3 vectorFinal = finalTransform.Transform(meshNode.vectorArray[i]);
            Vector3 normalFinal = normalTransform.Transform(meshNode.normalArray[i]);

            normalFinal.Normalize();

            meshFile.WriteLine("\t\t\t\t\t<vertex>");
            meshFile.WriteLine("\t\t\t\t\t\t<position x=\"" + vectorFinal.x + "\" y=\"" + vectorFinal.y + "\" z=\"" + 
                              vectorFinal.z + "\" />");

            if(!_flip)
            {
                meshFile.WriteLine("\t\t\t\t\t\t<normal x=\"" + normalFinal.x + "\" y=\"" + normalFinal.y + "\" z=\"" + 
                                  normalFinal.z + "\" />");
            }
            else
            {
                meshFile.WriteLine("\t\t\t\t\t\t<normal x=\"" + -normalFinal.x + "\" y=\"" + -normalFinal.y + "\" z=\"" + 
                                  -normalFinal.z + "\" />");
            }
            meshFile.WriteLine("\t\t\t\t\t</vertex>");
        }

        meshFile.WriteLine("\t\t\t\t</vertexbuffer>");

        meshFile.WriteLine("\t\t\t\t<vertexbuffer texture_coord_dimensions_0=\"2\" texture_coords=\"1\">");

        for(int i = 0; i < meshNode.vectorArray.Length; i++)
        {
            meshFile.WriteLine("\t\t\t\t\t<vertex>");
            meshFile.WriteLine("\t\t\t\t\t\t<texcoord u=\"" + meshNode.textureArray[i].u + "\" v=\"" + meshNode.textureArray[i].v + "\" />");
            meshFile.WriteLine("\t\t\t\t\t</vertex>");
        }

        meshFile.WriteLine("\t\t\t\t</vertexbuffer>");

        meshFile.WriteLine("\t\t\t</geometry>");

        meshFile.WriteLine("\t\t</submesh>");
    }

    static void ParseNodes(BinaryReader br, Hashtable ht)
    {
        int nodeType = br.ReadInt16();
        string nodeName = ReadIdentifier(br);
        string nodeParent = ReadIdentifier(br);

        Matrix4X4 mat = Read3X4Matrix(br);

        BaseNode node = null;

        if(nodeType == 0) // Null node is kosher.
        {
            node = new NullNode();
        }
        if(nodeType == 11) // LOD control node is empty
        {
            node = new LodNode();
        }
        if(nodeType == 3) // Sprite node
        {
            node = new SpriteNode();
        }
        if(nodeType == 12)
        {
            node = new EmitterNode();
            ReadIdentifier(br);
        }

        if(nodeType == 1)
        {
            Console.WriteLine("Mesh: " + nodeName);
            node = ParseMeshNode(br, mat);
        }

        if(node != null)
        {
            node.Name = nodeName;
            node.Matrix = mat;
            node.ParentName = nodeParent;

            ht[nodeName] = node;
            if(nodeParent != "")
            {
                ((BaseNode)ht[nodeParent]).Children.Add(node);
            }
        }
        else
        {
            Console.WriteLine("Error - Unknown node type: " + nodeType);
        }
    }

    static void ParseMaterials(BinaryReader br, Hashtable ht)
    {
        SODMaterial material = new SODMaterial();

        material.Name = ReadIdentifier(br);
        material.flAR = br.ReadSingle();
        material.flAG = br.ReadSingle();
        material.flAB = br.ReadSingle();
        material.flDR = br.ReadSingle();
        material.flDG = br.ReadSingle();
        material.flDB = br.ReadSingle();
        material.flSR = br.ReadSingle();
        material.flSG = br.ReadSingle();
        material.flSB = br.ReadSingle();
        material.flShininess = br.ReadSingle();
        material.bModel = br.ReadByte();

        Console.WriteLine("Lighting Model: " + material.bModel);

        if(s_flVersion > 1.8)
        {
            byte bUnknown = br.ReadByte();
        }

        ht[material.Name] = material;
    }

    static void RemoveNodeFromHashtable(string strNodeName, Hashtable ht)
    {
        BaseNode baseNode = (BaseNode)ht[strNodeName];

        for(int i = 0; i < baseNode.Children.Count; i++)
        {
            RemoveNodeFromHashtable(((BaseNode)baseNode.Children[i]).Name, ht);
        }
        BaseNode parentNode = (BaseNode)ht[baseNode.ParentName];
        parentNode.Children.Remove(baseNode);
        ht.Remove(strNodeName);
    }

    static int Main(string[] strArgs)
    {
        if(strArgs.Length < 1)
        {
            Console.WriteLine("Usage: SODToRaw <FileName> <flipnormals>");
            return 1;
        }

        for(int i = 1; i < strArgs.Length; i++)
        {
            _flip = true;
        }

        BinaryReader br = new BinaryReader(File.OpenRead(strArgs[0]));

        s_strFileBase = strArgs[0].Split('.')[0];

        TextWriter meshFile = File.CreateText(s_strFileBase + ".mesh.xml");

        meshFile.WriteLine("<mesh>");

        for(int i = 0; i < 10; i++)
        {
            br.ReadByte();
        }

        s_flVersion = br.ReadSingle();

        int lightingCount = br.ReadInt16();

        Hashtable htMaterials = new Hashtable();
        for(int i = 0; i < lightingCount; i++)
        {
            ParseMaterials(br, htMaterials);
        }

        int nodeCount = br.ReadInt16();

        Hashtable nodeTable = new Hashtable();
        for(int i = 0; i < nodeCount; i++)
        {
            ParseNodes(br, nodeTable);
        }


        bool isMaxLODAssigned = false;
        for(int i = 0; i < 10; i++)
        {
            BaseNode baseNode = nodeTable["lod" + i] as BaseNode;

            if(baseNode != null)
            {
                if(!isMaxLODAssigned)
                {
                    isMaxLODAssigned = true;
                }
                else
                {
                    RemoveNodeFromHashtable("lod" + i, nodeTable);
                }
            }
        }

        RemoveNodeFromHashtable("damage", nodeTable);

        Matrix4X4 mat = GenerateMeshFixupTransform(nodeTable);
        Matrix4X4 rotateX = Matrix4X4.RotateX(-3.14f / 2.0f);
        Matrix4X4 rotateZ = Matrix4X4.RotateZ(3.14f);
        Matrix4X4 matrixRot = Matrix4X4.Mult(rotateX, rotateZ);
        Matrix4X4 finalTransform = Matrix4X4.Mult(mat, matrixRot);

        meshFile.WriteLine("\t<submeshes>");

        Hashtable htTextureMaterials = new Hashtable();
        foreach(object key in nodeTable.Keys)
        {
            Console.WriteLine("Node Key " + key);

            if(nodeTable[key] is MeshNode)
            {
                int hierarchyCount = 0;
                BaseNode parentNode = (BaseNode) nodeTable[((MeshNode)nodeTable[key]).ParentName];

                while(parentNode != null)
                {
                    parentNode = (BaseNode) nodeTable[parentNode.ParentName];
                    hierarchyCount++;
                }

                ExportMeshNode((MeshNode)nodeTable[key], finalTransform, matrixRot, htTextureMaterials, meshFile);
            }
        }

        meshFile.WriteLine("\t</submeshes>");

        meshFile.WriteLine("</mesh>");

        meshFile.Flush();
        meshFile.Close();


        TextWriter materialFile = File.CreateText(s_strFileBase + ".material");

        foreach (object key in htTextureMaterials.Keys) 
        {
            string strTexture = (string) key;
            string strMaterialName = (string)htTextureMaterials[key];

            materialFile.WriteLine("material " + strMaterialName);
            materialFile.WriteLine("{");

            materialFile.WriteLine("\ttechnique");
            materialFile.WriteLine("\t{");

            materialFile.WriteLine("\t\tpass");
            materialFile.WriteLine("\t\t{");

            materialFile.WriteLine("\t\t\ttexture_unit");
            materialFile.WriteLine("\t\t\t{");

            materialFile.WriteLine("\t\t\t\ttexture " + strTexture + ".tga");

            materialFile.WriteLine("\t\t\t}");
            materialFile.WriteLine("\t\t}");
            materialFile.WriteLine("\t}");
            materialFile.WriteLine("}");
        }

        materialFile.Flush();
        materialFile.Close();

        TextWriter extrasFile = File.CreateText(s_strFileBase + ".extras");

        extrasFile.WriteLine("<Extras>");

        BaseNode hardpointNode = (BaseNode) nodeTable["hardpoints"];
        if(hardpointNode != null || hardpointNode.Children.Count == 0)
        {
            extrasFile.WriteLine("<Hardpoints>");
            foreach(BaseNode hardpoint in hardpointNode.Children)
            {
                Matrix4X4 hardpointTransform = Matrix4X4.Mult(hardpoint.Matrix, finalTransform);
                Vector3 vectorZero;
                Vector3 vectorUnit;

                vectorZero.x = 0.0f; vectorZero.y = 0.0f; vectorZero.z = 0.0f;
                vectorUnit.x = 0.0f; vectorUnit.y = 0.0f; vectorUnit.z = -1.0f;

                Vector3 hardpointLocation = hardpointTransform.Transform(vectorZero);
                Vector3 hardpointOffset =  hardpointTransform.Transform(vectorUnit);

                hardpointOffset.x -= hardpointLocation.x;
                hardpointOffset.y -= hardpointLocation.y;
                hardpointOffset.z -= hardpointLocation.z;

                hardpointOffset.Normalize();

                extrasFile.WriteLine("\t<Hardpoint Name=\"" + hardpoint.Name + "\">");
                extrasFile.WriteLine("\t\t<Location x=\"" + hardpointLocation.x + "\" y=\"" +  hardpointLocation.y + "\" z=\"" + hardpointLocation.z + "\" />");
                extrasFile.WriteLine("\t\t<Normal x=\"" + hardpointOffset.x + "\" y=\"" +  hardpointOffset.y + "\" z=\"" + hardpointOffset.z + "\" />");
                extrasFile.WriteLine("\t</Hardpoint>");
            }
            extrasFile.WriteLine("</Hardpoints>");
        }


        BaseNode lightsNode = (BaseNode) nodeTable["lights"];
        if(lightsNode != null || lightsNode.Children.Count == 0)
        {
            extrasFile.WriteLine("<Lights>");
            foreach(BaseNode light in lightsNode.Children)
            {
                Matrix4X4 lightTransform = Matrix4X4.Mult(light.Matrix, finalTransform);
                Vector3 vectorZero;
                Vector3 vectorUnit;

                vectorZero.x = 0.0f; vectorZero.y = 0.0f; vectorZero.z = 0.0f;
                vectorUnit.x = 0.0f; vectorUnit.y = 0.0f; vectorUnit.z = -1.0f;

                Vector3 lightLocation = lightTransform.Transform(vectorZero);
                Vector3 lightOffset =  lightTransform.Transform(vectorUnit);

                lightOffset.x -= lightLocation.x;
                lightOffset.y -= lightLocation.y;
                lightOffset.z -= lightLocation.z;

                lightOffset.Normalize();

                extrasFile.WriteLine("\t<light Name=\"" + light.Name + "\">");
                extrasFile.WriteLine("\t\t<Location x=\"" + lightLocation.x + "\" y=\"" +  lightLocation.y + "\" z=\"" + lightLocation.z + "\" />");
                extrasFile.WriteLine("\t\t<Normal x=\"" + lightOffset.x + "\" y=\"" +  lightOffset.y + "\" z=\"" + lightOffset.z + "\" />");
                extrasFile.WriteLine("\t</light>");
            }
            extrasFile.WriteLine("</Lights>");
        }

        extrasFile.WriteLine("</Extras>");

        extrasFile.Flush();
        extrasFile.Close();

        return 0;
    }

    static float s_flVersion;
}
